﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace DSServer.Communication
{
    class RoomI : RoomDisp_
    {
        private IDictionary<string,RoomEventListenerPrx> listeners = new Dictionary<string,RoomEventListenerPrx>();
        private IList<RoomEvent> events = new List<RoomEvent>();
        private readonly string accountDirectory = "CommunicationAccounts";
        private readonly string roomDirectory = "CommunicationRooms";
        private bool isLoaded = false;

        private string accountPath(string filename)
        {
            return accountDirectory + "/" + filename;
        }
        private string name;

        internal void LoadEvents()
        {
            if (new FileInfo(name).Length == 0)
                isLoaded = true; //fresh room;

            if (isLoaded) 
                return;           
                
            
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream(name, FileMode.Open, FileAccess.Read, FileShare.Read);
            events = (IList<RoomEvent>)formatter.Deserialize(stream);
            stream.Close();
            
            isLoaded = true;
        }

        private static long GetUnixTimestamp()
        {   
            return (long)(DateTime.Now - new DateTime(1970, 1, 1).ToLocalTime()).TotalSeconds;
        }

        public override void speak(string login, string message, Ice.Current current__)
        {
            lock (listeners)
            {
                if (!listeners.ContainsKey(login))
                    throw new UserNotJoinedException();
                Brodcast(new RoomEvent { 
                    who = login,
                    type = RoomEventType.SPEAK,
                    timestamp = GetUnixTimestamp(),
                    message = message });
            }
        }

        public override void leave(string login, Ice.Current current__)
        {
            lock (listeners)
            {
                LeaveImpl(login);
            }
        }

        public override void join(string login, string password, string name, Ice.Identity roomListenerIdentity, Ice.Current current__)
        {
            lock (listeners)
            {
                if (listeners.ContainsKey(login))
                    throw new UserAlreadyJoinedException();
                if (!File.Exists(accountPath(login)))
                    throw new InvalidLoginOrPasswordException();
                if (File.ReadAllText(accountPath(login)) != password)
                    throw new InvalidLoginOrPasswordException();

                this.name = roomDirectory + "/" + name;

                if (!File.Exists(this.name))
                    throw new RoomNotExistsException();                

                RoomEventListenerPrx listener = RoomEventListenerPrxHelper.uncheckedCast(current__.con.createProxy(roomListenerIdentity));
                listeners.Add(login, listener);

                LoadEvents();

                Brodcast(new RoomEvent
                {
                    message = null,
                    timestamp = GetUnixTimestamp(),
                    type = RoomEventType.JOIN,
                    who = login
                });
                
            }
        }

        public override RoomEvent[] getConversation(string login, Ice.Current current__)
        {
            lock (listeners)
            {
                if (!listeners.ContainsKey(login))
                    throw new UserNotJoinedException();
                return events.ToArray<RoomEvent>();
            }
        }

        public void AddListener(string login, RoomEventListenerPrx listener)
        {
            lock (listeners)
            {
                if(listeners.ContainsKey(login))
                    throw new UserAlreadyJoinedException();
                listeners.Add(login, listener);
                Brodcast(new RoomEvent {
                    message = null, 
                    timestamp = GetUnixTimestamp(),
                    type = RoomEventType.JOIN, 
                    who = login });
            }
        }


        /// <summary>
        /// call only when listeners is locked!!
        /// </summary>
        /// <param name="roomEvent"></param>
        private void Brodcast(RoomEvent roomEvent)
        {
            foreach (string login in listeners.Keys)
                if (login != roomEvent.who)
                {
                    try
                    {
                        listeners[login].onRoomEvent(roomEvent);
                    }
                    catch (Ice.CloseConnectionException e)
                    {
                        listeners.Remove(login);
                        LeaveImpl(login);
                    }
                }
            events.Add(roomEvent);
            SaveEvents();
        }

        /// <summary>
        /// call only when listeners is locked!!
        /// </summary>
        private void LeaveImpl(string login)
        {
            if (!listeners.ContainsKey(login))
                throw new UserNotJoinedException();
            listeners.Remove(login);
            Brodcast(new RoomEvent
            {
                who = login,
                type = RoomEventType.LEAVE,
                timestamp = GetUnixTimestamp(),
                message = null
            });
        }

        public void SaveEvents()
        {
            IFormatter formatter = new BinaryFormatter();
            Stream stream = new FileStream(name, FileMode.Create, FileAccess.Write, FileShare.None);
            formatter.Serialize(stream, events);
            stream.Close();
        }

        public bool IsEmpty()
        {
            lock (listeners)
            {
                return listeners.Any();
            }
        }       
    }
}
